//
// Created by ts on 2023/5/10.
//

#ifndef LEARN_VULKAN_VKHELPER_H
#define LEARN_VULKAN_VKHELPER_H

#include "Headers.h"
#include "VkBundle.h"
#include "Geometry.h"

typedef struct
{
    const char      *name;
    bool			validationOnly;
    bool			required;
} DriverFeature;

class VkHelper {
public:
    VkHelper() = delete;
    VkHelper(VkHelper&) = delete;
public:
    /** create Vulkan Instance */
    static void createInstance(bool bValidate, VkInstance *out_instance, VkDebugReportCallbackEXT *out_debugReport);

    /** pick physical device, create device, and get queue */
    static void pickPhyDevAndCreateDev(VkInstance instance, VkSurfaceKHR surface, DeviceInfo *out_deviceInfo, QueueInfo *out_queueInfo);

    /** create swapchain */
    static void createSwapchain(VkPhysicalDevice physicalDev, VkDevice device, VkSurfaceKHR surface,
                                uint16_t workQueueIndex, uint16_t presentQueueIndex,
                                SwapchainParam *out_swapchainParam, VkSwapchainKHR *out_swapchain);

    /** create render pass */
    static void createRenderPass(VkDevice device, SwapchainParam swapchainParam, VkRenderPass *out_renderPass);

    /** create pipeline cache */
    static void createPipelineCache(VkDevice device, VkPipelineCache *out_pipelineCache);

    /** create pipeline layout */
    static void createPipelineLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, VkPipelineLayout *out_pipelineLayout);

    /** create a shader module */
    static VkShaderModule createShaderModule(VkDevice device, std::vector<char> &code);

    /** create the graphic pipeline */
    static void createPipeline(VkDevice device, VkPipelineCache pipelineCache, VkPipelineLayout pipelineLayout,
                               VkRenderPass renderPass,
                               VkShaderModule vertShaderModule, VkShaderModule fragShaderModule,
                               SwapchainParam swapchainParam, VkPipeline *out_pipeline);

    /** create frame buffer */
    static void createFramebuffer(VkDevice device, VkRenderPass renderPass,
                                  uint32_t width, uint32_t height,
                                  uint32_t framebufferCount, VkImageView *imageViews, VkImageView depthImageView, VkImageView colorImageView,
                                  VkFramebuffer *out_framebuffers);

    /** create command pool */
    static void createCommandPool(VkDevice device, uint32_t workQueueIndex, VkCommandPool *out_cmdPool);

    /** allocate command buffers */
    static void allocateCommandBuffers(VkDevice device, VkCommandPool cmdPool, uint32_t cmdBufferCount, VkCommandBuffer *cmdBuffers);

    /** begin a command buffer, if use one time, this command buffer will use once */
    static void beginCommandBuffer(VkCommandBuffer cmdBuffer, bool oneTime);

    /** end a command buffer, if bFree is true, will free this command buffer */
    static void endCommandBuffer(VkCommandBuffer cmdBuffer, VkDevice device, VkCommandPool cmdPool, VkQueue queue, bool bFree);

    /** begin render pass */
    static void
    beginRenderPass(VkCommandBuffer cmdBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer,
                    uint32_t clearValueCount,
                    VkClearValue *clearValues, VkExtent2D extent);

    /** init geometry buffers */
    static void initGeometryBuffers(VkPhysicalDeviceMemoryProperties physicalMemoType, VkDevice device,
                                    VkCommandPool cmdPool, VkQueue queue, uint32_t maxFrameCount, CGeometry &geometry);

    /** bing render source, include pipeline、vertexbuffer、indexbuffer and so on. */
    static void bindRenderResource(VkCommandBuffer cmdBuffer, VkPipelineLayout layout, VkPipeline graphicPipeline, VkDescriptorSet descriptorSet, const CGeometry& geometry);

    /** command buffer draw a geometry */
    static void geometryDraw(VkCommandBuffer cmdBuffer, SwapchainParam swapchainParam, const CGeometry& geometry);

    /** end render pass */
    static void endRenderPass(VkCommandBuffer cmdBuffer);

    /** find will allocate memory's type */
    static uint32_t findMemoryType(VkPhysicalDeviceMemoryProperties physicalMemoType, uint32_t typeFilter, VkMemoryPropertyFlags properties);

    /** copy buffer */
    static void copyBuffer(VkDevice device, VkCommandPool cmdPool, VkQueue queue, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);

    /** create buffer implement */
    static void createBufferInternal(VkPhysicalDeviceMemoryProperties physicalMemoType, VkDevice device, VkBufferUsageFlags usage, VkMemoryPropertyFlags memProps, VkDeviceSize size,
                                     VkBuffer *outBuffer, VkDeviceMemory *outMemory);

    /** create buffer */
    static void createBuffer(VkPhysicalDeviceMemoryProperties physicalMemoType, VkDevice device, VkCommandPool cmdPool,
                             VkQueue queue, VkBufferUsageFlags usage, VkMemoryPropertyFlags memProps,
                             uint32_t dataSize, const void *data, VkBuffer *outBuffer,
                             VkDeviceMemory *outMemory);

    static void createBuffer(VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer *outBuffer);

    /** create descriptor set layout */
    static void createDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout *out_descriptorSetLayout);

    static void createDescriptorPool(VkDevice device, uint32_t maxFrameCount, VkDescriptorPool *out_descriptorPool);

    static void allocateDescriptorSets(VkDevice device, uint32_t maxFrameCount, VkDescriptorPool pool, VkDescriptorSetLayout layout,
                         VkDescriptorSet *out_descriptorSets);

    static void createImage(VkPhysicalDeviceMemoryProperties physicalMemoType, VkDevice device, int width, int height,
                int depth, VkImageType imageType, VkFormat format, VkSampleCountFlagBits sampleCount, VkImageTiling tiling, VkImageUsageFlags usage,
                VkImage *out_image, VkDeviceMemory *out_imageMemory);

    static void transitionImageLayout(VkDevice device, VkCommandPool cmdPool, VkQueue queue, VkImage image, VkFormat format, VkImageAspectFlags aspectMask,
                                      VkImageLayout oldLayout, VkImageLayout newLayout);

    static void copyBufferToImage(VkDevice device, VkCommandPool cmdPool, VkQueue queue, VkBuffer imageBuffer, VkImage image, int width, int height);

    static void createImageView(VkDevice device, VkImage image, VkImageViewType viewType, VkFormat format, VkImageAspectFlags aspectMask, VkImageView *out_imageView);

    static void createImageSampler(VkDevice device, VkPhysicalDeviceLimits physicalDevLimits, VkSampler *out_sampler);

    static VkFormat findSupportFormat(VkPhysicalDevice physicalDev, const std::vector<VkFormat>& candidates,
                                      VkImageTiling tiling, VkFormatFeatureFlags features);

    static VkFormat findDepthSupportFormat(VkPhysicalDevice physicalDev);

    static VkSampleCountFlagBits getUsableSampleCountBits(VkSampleCountFlags count);
};


#endif //LEARN_VULKAN_VKHELPER_H
